package com.openwater.tools.math;

import java.nio.FloatBuffer;

import android.opengl.Matrix;

import com.openwater.tools.BufferAllocator;

/**
 * Matrix utility
 * 
 * @author fuxp
 * @date 2012-6-8
 * @version 1.0 Copyright Corporation 2013,fuxp, All Rights Reserved
 * @since
 */
public final class MatrixState {

    /** Current Matrix */
    private static float[] mCurrMatrix = new float[16];
    /** 4x4 projection matrix */
    private static float[] mProjMatrix = new float[16];
    /** 4x4 mode view matrix */
    private static float[] mModeMatrix = new float[16];

    /** Position light data buffer */
    private static FloatBuffer mPosLightBuf = null;
    /** Direction light data Buffer */
    private static FloatBuffer mDirectLightBuf = null;
    /** Camera location data Buffer */
    private static FloatBuffer mCameraPosBuf = null;

    private static float[][] mStack = new float[10][16];

    private static int stackTop = -1;

    /**
     * initial the current matrix to identity
     */
    static {
        Matrix.setRotateM(mCurrMatrix, 0, 0, 1, 0, 0);
    }

    /**
     * push the current matrix to stack
     */
    public static void pushMatrix() {
        stackTop++;
        for (int i = 0; i < 16; i++) {
            mStack[stackTop][i] = mCurrMatrix[i];
        }
    }

    /**
     * pop the current matrix from stack
     */
    public static void popMatrix() {
        for (int i = 0; i < 16; i++) {
            mCurrMatrix[i] = mStack[stackTop][i];
        }
        stackTop--;
    }

    /**
     * Translates current matrix by x, y, and z in place.
     * 
     * @param x
     * @param y
     * @param z
     */
    public static void translate(float x, float y, float z) {
        Matrix.translateM(mCurrMatrix, 0, x, y, z);
    }

    /**
     * Rotates current matrix in place by angle a (in degrees) around the axis
     * (x, y, z).
     * 
     * @param angle
     * @param x
     * @param y
     * @param z
     */
    public static void rotate(float angle, float x, float y, float z) {
        Matrix.rotateM(mCurrMatrix, 0, angle, x, y, z);
    }

    /**
     * Scales current matrix in place by sx, sy, and sz.
     */
    public static void scale(float x, float y, float z) {
        Matrix.scaleM(mCurrMatrix, 0, x, y, z);
    }

    /**
     * set current matrix
     * 
     * @param useM
     */
    public static void setCurrM(float[] currM) {
        for (int i = 0; i < 16; i++) {
            mCurrMatrix[i] = currM[i];
        }
    }

    /**
     * get current matrix
     * 
     * @return
     */
    public static float[] getCurrM() {
        return mCurrMatrix;
    }

    /**
     * set camera position (cx,cy,cz) ,target position (tx,ty,tz) and camera's
     * up-direction (upx,upy,upz), and put camera position to
     * {@link #mCameraPosBuf} and used with {@link #getCameraPosBuf()}
     */
    public static void setLookAt(float cx, float cy, float cz, float tx, float ty, float tz,
            float upx, float upy, float upz) {
        Matrix.setLookAtM(mModeMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
        mCameraPosBuf = BufferAllocator.toFloatBuffer(new float[] { cx, cy, cz });
    }

    /**
     * Defines a projection matrix in terms of six clip planes.
     */
    public static void setFrustum(float left, float right, float bottom, float top, float near,
            float far) {
        Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    /**
     * Defines a projection matrix in 4 param
     * 
     * @param fovy
     *            eye's angle in Y axis
     * @param aspect
     *            width/height
     * @param near
     *            near plane
     * @param far
     *            far plane
     */
    public static void setPerspective(float fovy, float aspect, float near, float far) {
        final float frustum[] = perspective2frustum(fovy, aspect, near, far);
        setFrustum(frustum[0], frustum[1], frustum[2], frustum[3], frustum[4], frustum[5]);
    }

    /**
     * Computes an orthographic projection matrix.
     */
    public static void setOrtho(float left, float right, float bottom, float top, float near,
            float far) {
        Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    /**
     * calculate the final matrix that is : current matrix multiplying modeView
     * matrix multiplying project matrix
     * 
     * @return
     * */
    public static float[] getFinalMatrix() {
        float[] mMVPMatrix = new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, mModeMatrix, 0, mCurrMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }

    public static float[] getProjectMatrix(){
        return mProjMatrix;
    }
    public static float[] getModeViewMatrix(){
        return mModeMatrix;
    }
    /**
     * calculate modeView matrix multiplying project matrix and Planar Shadow
     * will use it
     */
    public static float[] getViewProjMatrix() {
        float[] mMVPMatrix = new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mModeMatrix, 0);
        return mMVPMatrix;
    }

    /**
     * set positioning light location
     */
    public static void setPosLight(float x, float y, float z) {
        final float[] data = { x, y, z };
        mPosLightBuf = BufferAllocator.toFloatBuffer(data);
    }

    /**
     * set direction light location
     */
    public static void setDirectLight0(float x, float y, float z) {
        float data[] = { x, y, z };
        mDirectLightBuf = BufferAllocator.toFloatBuffer(data);
    }

    /**
     * @return get positioning light location
     */
    public static FloatBuffer getPosLightBuf() {
        return mPosLightBuf;
    }

    /**
     * @return get direction light location
     */
    public static FloatBuffer getDirectLight0Buf() {
        return mDirectLightBuf;
    }

    /**
     * @return get camera location
     */
    public static FloatBuffer getCameraPosBuf() {
        return mCameraPosBuf;
    }

    /**
     * @param fovy
     *            Open the eyes of Angle
     * @param aspect
     *            Aspect ratio
     * @param near
     *            near plane
     * @param far
     *            far plane
     * @return Defines a projection matrix in terms of six clip planes.
     */
    public static float[] perspective2frustum(float fovy, float aspect, float near, float far) {
        float a = fovy / 2.0f;
        float top = (float) (near * Math.tan(a));
        float left = top * aspect;
        float frustum[] = { -left, left, -top, top, near, far };
        return frustum;
    }
}
